package org.abc.fund.service.dataSourceCollection;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.abc.fund.entity.funds.FundBasicInfo;
import org.abc.fund.repository.funds.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * 批量数据采集服务
 * 支持多基金、多数据类型的批量采集，并提供灵活的更新策略
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class BatchDataCollectionService {

    private final JoinQuantDataCollectionOrchestratorService orchestratorService;
    private final FundBasicInfoRepository fundBasicInfoRepository;
    private final FundDataPersistenceService fundDataPersistenceService;
    
    // 用于查询最新数据日期的Repository
    private final FundNetValueHistoryRepository fundNetValueHistoryRepository;
    private final FundFinIndicatorRepository fundFinIndicatorRepository;
    private final FundDividendRepository fundDividendRepository;
    private final FundMfDailyProfitRepository fundMfDailyProfitRepository;
    private final FundMtssRepository fundMtssRepository;

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final Set<String> SMALL_UPDATE_DATA_TYPES =
            Set.of("FUND_NET_VALUE", "FUND_MF_DAILY_PROFIT");
    
    // 线程池用于并发采集
    private final ExecutorService executorService = Executors.newFixedThreadPool(5);

    /**
     * 批量采集配置
     */
    @lombok.Data
    public static class BatchCollectionConfig {
        /**
         * 聚宽账号信息
         */
        private String username;
        private String password;

        /**
         * 基金代码列表（如果为空，则采集所有基金）
         */
        private List<String> fundCodes;

        /**
         * 数据类型列表（如：FUND_NET_VALUE, FUND_FIN_INDICATOR等）
         */
        private List<String> dataTypes;

        /**
         * 批量采集模式
         */
        private BatchMode mode = BatchMode.AUTO_UPDATE;

        /**
         * 采集策略
         */
        private CollectionStrategy strategy = CollectionStrategy.INCREMENTAL;

        /**
         * 时间范围配置（根据策略不同而不同）
         */
        private DateRangeConfig dateRange;

        /**
         * 是否并发采集（默认true）
         */
        private boolean concurrent = true;

        /**
         * 批次大小（每批采集的基金数量，默认10）
         */
        private int batchSize = 10;

        /**
         * 是否在写入前删除同时间窗口的数据
         * 默认：根据数据类型自动判断
         */
        private Boolean deleteOldBeforeInsert;
    }

    private boolean shouldDeleteBeforeInsert(BatchCollectionConfig config, String dataType, BatchMode mode) {
        if (mode == BatchMode.MANUAL_FETCH) {
            return false;
        }
        if (config.getDeleteOldBeforeInsert() != null) {
            return config.getDeleteOldBeforeInsert();
        }
        return (mode == BatchMode.AUTO_UPDATE || mode == BatchMode.MANUAL_UPDATE)
                && SMALL_UPDATE_DATA_TYPES.contains(dataType);
    }

    private void cleanupExistingData(String fundCode, String dataType, DateRange range) {
        if (range == null || range.startDate == null || range.endDate == null
                || range.endDate.isBefore(range.startDate)) {
            return;
        }
        fundDataPersistenceService.deleteDataInRange(dataType, fundCode, range.startDate, range.endDate);
    }

    public enum BatchMode {
        INIT,
        AUTO_UPDATE,
        MANUAL_UPDATE,
        MANUAL_FETCH
    }

    /**
     * 采集策略枚举
     */
    public enum CollectionStrategy {
        /**
         * 增量采集：从数据库中最新日期开始，采集到当前日期
         * 如果数据库中没有数据，则从指定起始日期开始采集
         */
        INCREMENTAL,

        /**
         * 全量采集：采集指定时间范围内的所有数据（会覆盖已有数据）
         */
        FULL_RANGE,

        /**
         * 最近N天采集：采集最近N天的数据（会覆盖已有数据）
         */
        RECENT_DAYS
    }

    /**
     * 时间范围配置
     */
    @lombok.Data
    public static class DateRangeConfig {
        /**
         * 起始日期（格式：yyyy-MM-dd）
         * 对于INCREMENTAL策略：如果数据库中没有数据，则从此日期开始
         * 对于FULL_RANGE策略：采集的起始日期
         * 对于RECENT_DAYS策略：不使用此字段
         */
        private String startDate;

        /**
         * 结束日期（格式：yyyy-MM-dd，默认为当前日期）
         */
        private String endDate;

        /**
         * 最近N天（仅用于RECENT_DAYS策略）
         */
        private Integer recentDays;
    }

    /**
     * 批量采集结果
     */
    @lombok.Data
    public static class BatchCollectionResult {
        private boolean success;
        private String message;
        private int totalFunds;
        private int totalDataTypes;
        private int successCount;
        private int failureCount;
        private List<FundCollectionResult> fundResults = new ArrayList<>();
        private Map<String, Integer> dataTypeStats = new HashMap<>();
        private boolean manualFetch;
    }

    /**
     * 单个基金的采集结果
     */
    @lombok.Data
    public static class FundCollectionResult {
        private String fundCode;
        private boolean success;
        private String message;
        private Map<String, Integer> dataTypeSavedCount = new HashMap<>();
        private Map<String, List<Map<String, Object>>> rawDataByDataType = new HashMap<>();
    }

    /**
     * 执行批量采集
     */
    @Transactional
    public BatchCollectionResult executeBatchCollection(BatchCollectionConfig config) {
        BatchMode mode = config.getMode() != null ? config.getMode() : BatchMode.AUTO_UPDATE;
        log.info("开始批量数据采集，模式：{}，策略：{}，数据类型：{}，基金数量：{}",
                mode, config.getStrategy(),
                config.getDataTypes(), config.getFundCodes() != null ? config.getFundCodes().size() : "ALL");

        if (config.getDataTypes() == null || config.getDataTypes().isEmpty()) {
            BatchCollectionResult empty = new BatchCollectionResult();
            empty.setSuccess(false);
            empty.setMessage("数据类型参数不能为空");
            return empty;
        }

        BatchCollectionResult result = new BatchCollectionResult();
        result.setTotalDataTypes(config.getDataTypes().size());
        result.setManualFetch(mode == BatchMode.MANUAL_FETCH);

        // 1. 获取要采集的基金列表
        List<String> fundCodes = getFundCodesToCollect(config);
        result.setTotalFunds(fundCodes.size());

        if (fundCodes.isEmpty()) {
            result.setSuccess(false);
            result.setMessage("No funds found to collect");
            return result;
        }

        // 2. 计算每个基金的时间范围
        Map<String, DateRange> fundDateRanges = calculateDateRanges(fundCodes, config, mode);

        // 3. 执行采集
        if (mode == BatchMode.MANUAL_FETCH) {
            executeSequentialCollection(fundCodes, config, fundDateRanges, result, mode);
        } else if (config.isConcurrent()) {
            executeConcurrentCollection(fundCodes, config, fundDateRanges, result, mode);
        } else {
            executeSequentialCollection(fundCodes, config, fundDateRanges, result, mode);
        }
        result.setSuccess(result.getFailureCount() == 0);
        result.setMessage(String.format("批量采集完成。成功：%d，失败：%d",
                result.getSuccessCount(), result.getFailureCount()));

        return result;
    }

    /**
     * 获取要采集的基金代码列表
     */
    private List<String> getFundCodesToCollect(BatchCollectionConfig config) {
        if (config.getFundCodes() != null && !config.getFundCodes().isEmpty()) {
            return config.getFundCodes();
        }

        BatchMode mode = config.getMode() != null ? config.getMode() : BatchMode.AUTO_UPDATE;
        if (mode == BatchMode.MANUAL_FETCH) {
            throw new IllegalArgumentException("手动拉取模式必须指定基金代码列表");
        }

        log.info("未提供基金代码，将改为采集数据库中的全部基金");
        return fundBasicInfoRepository.findAll().stream()
                .map(FundBasicInfo::getCode)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
    }

    /**
     * 计算每个基金的时间范围
     */
    private Map<String, DateRange> calculateDateRanges(List<String> fundCodes,
                                                      BatchCollectionConfig config,
                                                      BatchMode mode) {
        Map<String, DateRange> ranges = new HashMap<>();
        for (String fundCode : fundCodes) {
            ranges.put(fundCode, determineRangeForFund(fundCode, config, mode));
        }
        return ranges;
    }

    private DateRange determineRangeForFund(String fundCode,
                                            BatchCollectionConfig config,
                                            BatchMode mode) {
        DateRangeConfig dateRangeConfig = config.getDateRange();
        LocalDate today = LocalDate.now();

        if (mode == BatchMode.AUTO_UPDATE && dateRangeConfig == null) {
            return createDefaultAutoUpdateRange();
        }
        if ((mode == BatchMode.MANUAL_FETCH || mode == BatchMode.MANUAL_UPDATE)
                && dateRangeConfig == null) {
            return createDefaultManualRange();
        }
        CollectionStrategy strategy = config.getStrategy();
        DateRange range = new DateRange();

        switch (strategy) {
            case INCREMENTAL:
                LocalDate latestDate = getLatestDateForFund(fundCode, config.getDataTypes());
                if (latestDate != null) {
                    range.startDate = latestDate.plusDays(1);
                    range.endDate = today;
                } else if (dateRangeConfig != null && dateRangeConfig.getStartDate() != null) {
                    range.startDate = LocalDate.parse(dateRangeConfig.getStartDate(), DATE_FORMATTER);
                    range.endDate = dateRangeConfig.getEndDate() != null
                            ? LocalDate.parse(dateRangeConfig.getEndDate(), DATE_FORMATTER)
                            : today;
                } else {
                    range.startDate = today.minusDays(365);
                    range.endDate = today;
                }
                break;
            case FULL_RANGE:
                if (dateRangeConfig != null && dateRangeConfig.getStartDate() != null) {
                    range.startDate = LocalDate.parse(dateRangeConfig.getStartDate(), DATE_FORMATTER);
                    range.endDate = dateRangeConfig.getEndDate() != null
                            ? LocalDate.parse(dateRangeConfig.getEndDate(), DATE_FORMATTER)
                            : today;
                } else {
                    range.startDate = today.minusDays(365);
                    range.endDate = today;
                }
                break;
            case RECENT_DAYS:
                int days = dateRangeConfig != null && dateRangeConfig.getRecentDays() != null
                        ? dateRangeConfig.getRecentDays()
                        : 7;
                range.startDate = today.minusDays(days);
                range.endDate = today;
                break;
            default:
                range = createDefaultAutoUpdateRange();
        }
        return clampToAvailableWindow(range);
    }

    private DateRange createDefaultAutoUpdateRange() {
        DateRange range = new DateRange();
        LocalDate today = LocalDate.now();
        LocalDate start = today.minusMonths(4).withDayOfMonth(1);
        LocalDate end = start.plusMonths(1).minusDays(1);
        range.startDate = start;
        range.endDate = end;
        return clampToAvailableWindow(range);
    }

    private DateRange createDefaultManualRange() {
        DateRange range = new DateRange();
        LocalDate today = LocalDate.now();
        range.startDate = today.minusDays(30);
        range.endDate = today;
        return clampToAvailableWindow(range);
    }

    private static final int MAX_LOOKBACK_MONTHS = 15;
    private static final int MIN_RECENT_MONTHS = 3;
    private static final int RECENT_BUFFER_DAYS = 1;
    private static final int FALLBACK_WINDOW_DAYS = 7;

    private DateRange clampToAvailableWindow(DateRange range) {
        if (range == null) {
            return null;
        }
        LocalDate today = LocalDate.now();
        LocalDate min = today.minusMonths(MAX_LOOKBACK_MONTHS);
        LocalDate max = today.minusMonths(MIN_RECENT_MONTHS).minusDays(RECENT_BUFFER_DAYS);
        if (max.isBefore(min)) {
            max = min.plusDays(FALLBACK_WINDOW_DAYS);
        }
        if (range.startDate != null && range.startDate.isBefore(min)) {
            range.startDate = min;
        }
        if (range.endDate != null && range.endDate.isAfter(max)) {
            range.endDate = max;
        }
        if (range.startDate != null && range.endDate != null && range.startDate.isAfter(range.endDate)) {
            range.startDate = max.minusDays(FALLBACK_WINDOW_DAYS - 1);
            if (range.startDate.isBefore(min)) {
                range.startDate = min;
            }
            range.endDate = max;
        }
        return range;
    }

    /**
     * 获取基金在指定数据类型中的最新日期
     */
    private LocalDate getLatestDateForFund(String fundCode, List<String> dataTypes) {
        LocalDate latestDate = null;

        for (String dataType : dataTypes) {
            LocalDate date = null;
            try {
                switch (dataType) {
                    case "FUND_NET_VALUE":
                    case "FUND_EXTRAS":
                        date = fundNetValueHistoryRepository.findTopByFundCodeOrderByNetDateDesc(fundCode)
                                .map(f -> f.getNetDate())
                                .orElse(null);
                        break;
                    case "FUND_FIN_INDICATOR":
                        date = fundFinIndicatorRepository.findTopByFundCodeOrderByStatisticDateDesc(fundCode)
                                .map(f -> f.getStatisticDate())
                                .orElse(null);
                        break;
                    case "FUND_DIVIDEND":
                        date = fundDividendRepository.findTopByFundCodeOrderByPubDateDesc(fundCode)
                                .map(f -> f.getPubDate())
                                .orElse(null);
                        break;
                    case "FUND_MF_DAILY_PROFIT":
                        date = fundMfDailyProfitRepository.findTopByFundCodeOrderByEndDateDesc(fundCode)
                                .map(f -> f.getEndDate())
                                .orElse(null);
                        break;
                    case "FUND_MTSS":
                        date = fundMtssRepository.findTopByFundCodeOrderByDateDesc(fundCode)
                                .map(f -> f.getDate())
                                .orElse(null);
                        break;
                }
            } catch (Exception e) {
                log.warn("获取基金 {} 的数据类型 {} 最新日期失败：{}", fundCode, dataType, e.getMessage());
            }

            if (date != null && (latestDate == null || date.isAfter(latestDate))) {
                latestDate = date;
            }
        }

        return latestDate;
    }

    /**
     * 并发采集
     */
    private void executeConcurrentCollection(List<String> fundCodes, BatchCollectionConfig config,
                                            Map<String, DateRange> fundDateRanges, BatchCollectionResult result,
                                            BatchMode mode) {
        List<CompletableFuture<FundCollectionResult>> futures = new ArrayList<>();

        for (String fundCode : fundCodes) {
            CompletableFuture<FundCollectionResult> future = CompletableFuture.supplyAsync(() -> {
                return collectFundData(fundCode, config, fundDateRanges.get(fundCode), mode);
            }, executorService);

            futures.add(future);
        }

        // 等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        // 收集结果
        for (CompletableFuture<FundCollectionResult> future : futures) {
            try {
                FundCollectionResult fundResult = future.get();
                result.getFundResults().add(fundResult);
                if (fundResult.isSuccess()) {
                    result.setSuccessCount(result.getSuccessCount() + 1);
                } else {
                    result.setFailureCount(result.getFailureCount() + 1);
                }

                // 统计各数据类型的保存数量
                fundResult.getDataTypeSavedCount().forEach((dataType, count) -> {
                    result.getDataTypeStats().merge(dataType, count, Integer::sum);
                });
            } catch (Exception e) {
                log.error("汇总基金采集结果失败：{}", e.getMessage(), e);
                result.setFailureCount(result.getFailureCount() + 1);
            }
        }
    }

    /**
     * 顺序采集
     */
    private void executeSequentialCollection(List<String> fundCodes, BatchCollectionConfig config,
                                            Map<String, DateRange> fundDateRanges, BatchCollectionResult result,
                                            BatchMode mode) {
        for (String fundCode : fundCodes) {
            FundCollectionResult fundResult = collectFundData(fundCode, config, fundDateRanges.get(fundCode), mode);
            result.getFundResults().add(fundResult);

            if (fundResult.isSuccess()) {
                result.setSuccessCount(result.getSuccessCount() + 1);
            } else {
                result.setFailureCount(result.getFailureCount() + 1);
            }

            // 统计各数据类型的保存数量
            fundResult.getDataTypeSavedCount().forEach((dataType, count) -> {
                result.getDataTypeStats().merge(dataType, count, Integer::sum);
            });
        }
    }

    /**
     * 采集单个基金的数据
     */
    private FundCollectionResult collectFundData(String fundCode,
                                                BatchCollectionConfig config,
                                                DateRange dateRange,
                                                BatchMode mode) {
        FundCollectionResult result = new FundCollectionResult();
        result.setFundCode(fundCode);

        if (dateRange == null) {
            dateRange = createDefaultManualRange();
        }

        boolean persist = mode != BatchMode.MANUAL_FETCH;
        boolean includeRawData = true;

        try {
            for (String dataType : config.getDataTypes()) {
                if (persist && shouldDeleteBeforeInsert(config, dataType, mode)) {
                    cleanupExistingData(fundCode, dataType, dateRange);
                }

                // FUND_EXTRAS 需要为每种 info_type 分别调用
                if ("FUND_EXTRAS".equals(dataType)) {
                    // 为三种类型分别采集：累计净值、单位净值、复权净值
                    String[] infoTypes = {"acc_net_value", "unit_net_value", "adj_net_value"};
                    int totalSavedCount = 0;
                    
                    for (String infoType : infoTypes) {
                        Map<String, Object> params = new HashMap<>();
                        params.put("data_type", dataType);
                        params.put("fund_codes", Arrays.asList(fundCode));
                        params.put("start_date", dateRange.startDate.format(DATE_FORMATTER));
                        params.put("end_date", dateRange.endDate.format(DATE_FORMATTER));
                        params.put("info_type", infoType);
                        params.put("count", null); // count 参数可选

                        JoinQuantDataCollectionOrchestratorService.CollectionResult collectionResult =
                                orchestratorService.collectData(
                                        config.getUsername(),
                                        config.getPassword(),
                                        params,
                                        persist,
                                        includeRawData);

                        if (collectionResult.isSuccess()) {
                            int savedCount = collectionResult.getSavedRecords() != null ? collectionResult.getSavedRecords() : 0;
                            totalSavedCount += savedCount;
                            log.info("基金 {} 数据类型 {} (info_type={}) 采集成功 {} 条记录，是否持久化：{}",
                                    savedCount, fundCode, dataType, infoType, persist);
                        } else {
                            log.warn("基金 {} 数据类型 {} (info_type={}) 采集失败：{}", 
                                    fundCode, dataType, infoType, collectionResult.getMessage());
                        }
                    }
                    
                    result.getDataTypeSavedCount().put(dataType, totalSavedCount);
                } else {
                    Map<String, Object> params = new HashMap<>();
                    params.put("data_type", dataType);
                    params.put("fund_codes", Arrays.asList(fundCode));
                    params.put("start_date", dateRange.startDate.format(DATE_FORMATTER));
                    params.put("end_date", dateRange.endDate.format(DATE_FORMATTER));

                    JoinQuantDataCollectionOrchestratorService.CollectionResult collectionResult =
                            orchestratorService.collectData(
                                    config.getUsername(),
                                    config.getPassword(),
                                    params,
                                    persist,
                                    includeRawData);

                    if (collectionResult.isSuccess()) {
                        int savedCount = collectionResult.getSavedRecords() != null ? collectionResult.getSavedRecords() : 0;
                        result.getDataTypeSavedCount().put(dataType, savedCount);
                        if (includeRawData && collectionResult.getRawData() != null) {
                            // 限制预览为 3 条
                            result.getRawDataByDataType().put(
                                    dataType,
                                    collectionResult.getRawData().stream().limit(3).toList()
                            );
                        }
                        log.info("基金 {} 数据类型 {} 采集成功 {} 条记录，是否持久化：{}",
                                savedCount, fundCode, dataType, persist);
                    } else {
                        log.warn("基金 {} 数据类型 {} 采集失败：{}", 
                                fundCode, dataType, collectionResult.getMessage());
                    }
                }
            }

            result.setSuccess(true);
            result.setMessage("采集完成");
        } catch (Exception e) {
            log.error("基金 {} 采集数据时发生异常：{}", fundCode, e.getMessage(), e);
            result.setSuccess(false);
            result.setMessage("错误：" + e.getMessage());
        }

        return result;
    }

    /**
     * 时间范围内部类
     */
    private static class DateRange {
        LocalDate startDate;
        LocalDate endDate;
    }
}

